package logfct

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/robfig/cron/v3"
	"github.com/sirupsen/logrus"
	"gopkg.in/ini.v1"
	"gopkg.in/natefinch/lumberjack.v2"
)

var mutex sync.RWMutex
var logsMap = make(map[string]*logrus.Logger)
var logsExpiryDay = make(map[string]int)
var loggerPath string
var defaultLogger *logrus.Logger

func init() {

	// 获取可执行文件的绝对路径
	execPath, err := os.Executable()
	if err != nil {
		log.Printf("获取可执行文件路径失败，無法加載Log設定文件: %s", err.Error())
	}

	// 獲取執行路徑，构建配置文件的完整路径
	configPath := filepath.Join(filepath.Dir(execPath), "config/log.ini")

	cfg, err := ini.Load(configPath)
	if err != nil {
		log.Printf("加載Log設定文件失敗: %s", err.Error())
	}

	//讀取公共的配置項，用於設置日志文件的保存路徑
	loggerPath = cfg.Section("logger-common-setting").Key("logger.path").String()
	if loggerPath == "" {
		log.Printf("未設置日志文件保存路徑，使用默認路徑: %s", loggerPath)
		loggerPath = "logs"
	}
	if !filepath.IsAbs(loggerPath) {
		loggerPath = filepath.Join(filepath.Dir(execPath), loggerPath)
	}
	//讀取自定義的日志對象配置項，初始化需要使用的日志對象
	for _, section := range cfg.Sections() {
		if strings.Contains(section.Name(), "logger-custom-") {
			name := section.Key("logger.name").String()
			maxAge, _ := section.Key("logger.maxAge").Int()
			maxSize, _ := section.Key("logger.maxSize").Int()
			isDefault, _ := section.Key("logger.default").Bool()
			logPath := filepath.Join(loggerPath, name) + ".log"
			log := createLogger(logPath, &LogConfig{
				MaxAge:    maxAge,
				MaxSize:   maxSize,
				IsDefault: isDefault,
			})
			logsMap[name] = log
			if isDefault {
				defaultLogger = log
			}

			logsExpiryDay[name] = maxAge
		}
	}

	if defaultLogger == nil {
		defaultLogger = New("default")
		logsMap["default"] = defaultLogger
		log.Printf("未設置默認的日志對象\r\n")
	}

	cleanupExpiredLogs()
	startCleanupCron()
}

// NewWithConfig 创建一个新的日志对象
//
// 該方法一般只用於配置文件中沒有對對應的日志對象進行定義時
// 使用傳入的配置參數創建一個日志對象
func NewWithConfig(logName string, config *LogConfig) *logrus.Logger {
	log := logsMap[logName]
	if log != nil {
		return log
	}
	logPath := filepath.Join(loggerPath, logName) + ".log"

	mutex.Lock()
	defer mutex.Unlock()

	// 再次检查，防止重复创建
	log = logsMap[logName]
	if log != nil {
		return log
	}
	log = createLogger(logPath, config)
	logsMap[logName] = log

	return log
}

// StartCleanupCron 启动定时清理任务
func startCleanupCron() {

	c := cron.New()

	// 分 时 日 月 周
	_, err := c.AddFunc("10 00 * * *", func() {
		log.Printf("开始执行日志清理任务...")
		// 遍历所有日志配置，执行清理
		cleanupExpiredLogs()
	})

	if err != nil {
		log.Printf("启动定时清理任务失败: %s", err)
		return
	}

	c.Start()
	log.Printf("日志定时清理任务已启动")
}
func cleanupExpiredLogs() {
	for logName, _ := range logsMap {
		maxAge := logsExpiryDay[logName]
		cleanupExpiredLog(logName, maxAge)
	}
}
func cleanupExpiredLog(logName string, maxAge int) {

	log.Printf("準備清理 %s 的日志文件，清理時間爲 %d 天之前的日志", logName, maxAge)

	dir, err := os.ReadDir(loggerPath)
	if err != nil {
		log.Printf("讀取日志文件列表失敗: %s", err)
		return
	}

	// 计算过期时间
	expiredTime := time.Now().Add(-time.Duration(maxAge) * 24 * time.Hour)

	for _, entry := range dir {
		if entry.IsDir() {
			continue
		}
		fileName := entry.Name()
		filePath := filepath.Join(loggerPath, fileName)

		//找到符合命名規則的日志文件
		if strings.Contains(fileName, logName+"-") {
			//提取日志文件的生產日期

			timeStr, err := extractDateFromFilename(fileName)
			if err != nil {
				continue
			}

			fileCreateTime, err := time.Parse("2006-01-02", timeStr)
			if err != nil {
				continue
			}

			if fileCreateTime.Before(expiredTime) {
				err := os.Remove(filePath)
				if err != nil {
					log.Printf("删除过期日志文件失败 %s: %v", filePath, err)
				} else {
					log.Printf("已删除过期日志文件: %s (创建日期: %s)", filePath, timeStr)
				}
			}
		}
	}
}

// 从日志文件名中提取日期
func extractDateFromFilename(filename string) (string, error) {
	// 正则表达式匹配 YYYY-MM-DD 格式的日期
	re := regexp.MustCompile(`(\d{4}-\d{2}-\d{2})`)
	matches := re.FindStringSubmatch(filename)

	if len(matches) > 1 {
		// 验证日期是否有效
		_, err := time.Parse("2006-01-02", matches[1])
		if err != nil {
			return "", fmt.Errorf("无效的日期格式: %v", err)
		}
		return matches[1], nil
	}

	return "", fmt.Errorf("未找到有效的日期")
}

// New 创建一个新的日志对象
//
// 該方法一般只用於配置文件中沒有對對應的日志對象進行定義時
// 使用默認的參數（日志文件留存30天，最大500MB）創建一個日志對象
func New(logName string) *logrus.Logger {
	return NewWithConfig(logName, &LogConfig{
		MaxAge:  30,
		MaxSize: 500,
	})
}

// Default 获取默认的日志对象
// 默認的日志對象由初始化時配置文件中配置的logger.default = true的節點
// 假如沒有配置對應的默認節點，則使用默認的配置項創建一個名稱default的節點并返回
// 假如有多個默認的節點，在初始化時會後續創建的對象會覆蓋之前的對象
func Default() *logrus.Logger {
	return defaultLogger
}

// createLogger 创建一个新的日志对象
func createLogger(logName string, config *LogConfig) *logrus.Logger {

	log := logrus.New()

	log.SetOutput(os.Stdout)

	dailyHook := newDailyFileHook(logName, config.MaxSize, config.MaxAge, true)
	log.AddHook(dailyHook)

	log.SetFormatter(&logrus.TextFormatter{
		FullTimestamp:   true,
		TimestampFormat: "2006-01-02 15:04:05.999",
		DisableColors:   true,
	})

	return log
}

// newDailyFileHook 创建一个新的DailyFileHook对象
func newDailyFileHook(filename string, maxSize, maxAge int, localtime bool) *DailyFileHook {
	hook := &DailyFileHook{
		filename:  filename,
		maxSize:   maxSize,
		maxAge:    maxAge,
		localtime: localtime,
	}
	hook.createWriter()
	return hook
}

// LogConfig 日誌配置,用於在初始化日志對象的時候，傳入對應的配置信息
type LogConfig struct {
	MaxSize   int
	MaxAge    int
	IsDefault bool
}

// DailyFileHook 自定義日志鈎子，用於拓展原有的logrus的功能，將日志輸出到文件，同時使用lumberjack根據日志的大小進行自動分割
type DailyFileHook struct {
	filename  string
	maxSize   int
	maxAge    int
	localtime bool
	writer    *lumberjack.Logger
	mu        sync.Mutex
	date      string
}

// createWriter 創建一個寫日志的對象
//
// 該對象會根據日誌的日誌文件名稱和日誌的日誌文件路徑創建，
// 同時在創建的時候，檢查是否已經存在一個write，假如存在，則關閉，避免舊文件一直被占用
func (h *DailyFileHook) createWriter() {
	dir := filepath.Dir(h.filename)
	base := filepath.Base(h.filename)
	ext := filepath.Ext(base)
	name := strings.TrimSuffix(base, ext)

	date := h.getCurrentDate()
	datedFilename := filepath.Join(dir, fmt.Sprintf("%s-%s%s", name, date, ext))

	defer func(writer *lumberjack.Logger) {
		if writer != nil {
			err := writer.Close()
			if err != nil {
				fmt.Printf("关闭日志文件失败: %v", err)
			}
		}
	}(h.writer)

	h.writer = &lumberjack.Logger{
		Filename:  datedFilename,
		MaxSize:   h.maxSize,
		MaxAge:    h.maxAge,
		LocalTime: h.localtime,
	}
}

func (h *DailyFileHook) Levels() []logrus.Level {
	return logrus.AllLevels
}

func (h *DailyFileHook) Fire(entry *logrus.Entry) error {
	h.mu.Lock()
	defer h.mu.Unlock()

	currentDate := h.getCurrentDate()
	if currentDate != h.date {
		h.date = currentDate
		h.createWriter()
	}

	line, _ := entry.String()
	_, err := h.writer.Write([]byte(line))
	return err
}

// getCurrentDate 获取当前日期
func (h *DailyFileHook) getCurrentDate() string {
	return time.Now().Format("2006-01-02")
}
